﻿// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Roslynator.CSharp.Analysis;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class NamedTypeSymbolAnalyzer : BaseDiagnosticAnalyzer
{
    private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
    {
        get
        {
            if (_supportedDiagnostics.IsDefault)
                Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.ImplementNonGenericCounterpart);

            return _supportedDiagnostics;
        }
    }

    public override void Initialize(AnalysisContext context)
    {
        base.Initialize(context);

        context.RegisterSymbolAction(f => AnalyzeNamedType(f), SymbolKind.NamedType);
    }

    private static void AnalyzeNamedType(SymbolAnalysisContext context)
    {
        if (context.Symbol.IsImplicitlyDeclared)
            return;

        var symbol = (INamedTypeSymbol)context.Symbol;

        TypeKind typeKind = symbol.TypeKind;

        if (typeKind == TypeKind.Class
            || typeKind == TypeKind.Struct)
        {
            if (symbol.IsPubliclyVisible())
            {
                ImmutableArray<INamedTypeSymbol> interfaces = symbol.Interfaces;

                if (interfaces.Any())
                {
                    var fIComparable = false;
                    var fIComparableOfT = false;
                    var fIComparer = false;
                    var fIComparerOfT = false;
                    var fIEqualityComparer = false;
                    var fIEqualityComparerOfT = false;

                    foreach (INamedTypeSymbol interfaceSymbol in interfaces)
                    {
                        switch (interfaceSymbol.MetadataName)
                        {
                            case "IComparable":
                            {
                                if (interfaceSymbol.HasMetadataName(MetadataNames.System_IComparable))
                                    fIComparable = true;

                                break;
                            }
                            case "IComparable`1":
                            {
                                if (interfaceSymbol.HasMetadataName(MetadataNames.System_IComparable_T))
                                    fIComparableOfT = true;

                                break;
                            }
                            case "IComparer":
                            {
                                if (interfaceSymbol.HasMetadataName(MetadataNames.System_Collections_IComparer))
                                    fIComparer = true;

                                break;
                            }
                            case "IComparer`1":
                            {
                                if (interfaceSymbol.HasMetadataName(MetadataNames.System_Collections_Generic_IComparer_T))
                                    fIComparerOfT = true;

                                break;
                            }
                            case "IEqualityComparer":
                            {
                                if (interfaceSymbol.HasMetadataName(MetadataNames.System_Collections_IEqualityComparer))
                                    fIEqualityComparer = true;

                                break;
                            }
                            case "IEqualityComparer`1":
                            {
                                if (interfaceSymbol.HasMetadataName(MetadataNames.System_Collections_Generic_IEqualityComparer_T))
                                    fIEqualityComparerOfT = true;

                                break;
                            }
                        }
                    }

                    if (fIComparableOfT
                        && !fIComparable)
                    {
                        ReportDiagnostic(context, symbol, "IComparable", "IComparable<T>");
                    }

                    if (fIComparerOfT
                        && !fIComparer)
                    {
                        ReportDiagnostic(context, symbol, "IComparer", "IComparer<T>");
                    }

                    if (fIEqualityComparerOfT
                        && !fIEqualityComparer)
                    {
                        ReportDiagnostic(context, symbol, "IEqualityComparer", "IEqualityComparer<T>");
                    }
                }
            }
        }
    }

    private static void ReportDiagnostic(SymbolAnalysisContext context, INamedTypeSymbol symbol, string interfaceName, string genericInterfaceName)
    {
        SyntaxNode node = symbol.GetSyntax(context.CancellationToken);

        SyntaxToken identifier = GetIdentifier();

        SyntaxToken GetIdentifier()
        {
            return node switch
            {
                ClassDeclarationSyntax classDeclaration => classDeclaration.Identifier,
                StructDeclarationSyntax structDeclaration => structDeclaration.Identifier,
                RecordDeclarationSyntax recordDeclaration => recordDeclaration.Identifier,
                _ => throw new InvalidOperationException($"Unknown syntax node kind '{node.Kind()}'."),
            };
        }

        DiagnosticHelpers.ReportDiagnostic(
            context,
            DiagnosticRules.ImplementNonGenericCounterpart,
            identifier.GetLocation(),
            ImmutableDictionary.CreateRange(new[] { new KeyValuePair<string, string>("InterfaceName", interfaceName) }),
            interfaceName,
            genericInterfaceName);
    }
}
